import Popover from "../../Popover";
import Draggable from "../../Draggable";
import { PropType, computed, defineComponent, onBeforeUnmount, onMounted, ref, watchEffect } from "vue";
import formFieldRef from "../../use/formFieldRef";

export interface SliderProps {
    range?: boolean,
    min?: number,
    max?: number,
    step?: number,
    modelValue?: number | number[],
    disabled?: boolean,
    tipFormatter?: (value: any) => any,
    marks?: any,
    asFormField?: boolean,
}

export default defineComponent({
    name: "Slider",
    props: {
        range: {type: Boolean as PropType<SliderProps['range']>},
        min: {type: Number as PropType<SliderProps['min']>},
        max: {type: Number as PropType<SliderProps['max']>},
        step: {type: Number as PropType<SliderProps['step']>},
        modelValue: {type: [Number, Array<number>] as PropType<SliderProps['modelValue']>},
        disabled: {type: Boolean as PropType<SliderProps['disabled']>},
        tipFormatter: {type: Function as PropType<SliderProps['tipFormatter']>},
        marks: {type: Object as PropType<SliderProps['marks']>},
        asFormField: {type: Boolean as PropType<SliderProps['asFormField']>, default: true},
    },
    emits: ['update:modelValue', 'change'],
    setup (props:SliderProps, { emit }) {
        let rail: any;
        let leftDrag: any;
        let rightDrag: any;
        let pop: any;
        let popRight: any;
        const wrap = ref();
        const min = props.min ?? 0;
        const max = props.max ?? 100;
        const step = props.step ?? 1.00;
        const range = props.range ?? false;
        const value = formFieldRef(props, emit, range ? [0, 0] : 0);

        const classList = computed(() => ({
            'cm-slider': true,
            'cm-slider-disabled': props.disabled
        }));

        const snap = () => {
            const rect = rail.getBoundingClientRect();
            const allW = rect.width;
            return allW / (max - min) * step;
        };

        // 根据值计算位置
        const calculateLeftRight = computed(() => {
            const val = range ? value.value : [min, value.value];
            const trackWidth = Math.abs(val[1] - val[0]) / (max - min) * 100;
            const trackLeft = (val[0] - min) / (max - min) * 100;
            const handleRight = (val[1] - min) / (max - min) * 100;

            return {left: trackLeft, width: trackWidth, right: handleRight};
        });

        const trackStyle = computed(() => {
            const ret = calculateLeftRight.value;
            return {left: ret.left + '%', width: ret.width + '%'};
        });

        const contextLeft = computed(() => {
            const v = range ? value.value[0] : value.value;
            if (props.tipFormatter) {
                return props.tipFormatter(v);
            }
            return `${v}`;
        });

        const contextRight = computed(() => {
            if (props.tipFormatter) {
                return props.tipFormatter(value.value[1]);
            }
            return `${value.value[1]}`;
        });

        const updateHandlerPosition = () => {
            if (!rail) {
                return;
            }

            const ret = calculateLeftRight.value;
            const rect = rail.getBoundingClientRect();

            const leftX = range ? rect.width * ret.left / 100 : rect.width * ret.right / 100;
            const rightX = range ? rect.width * (ret.left + ret.width) / 100 : 0;

            if (leftDrag) {
                leftDrag.setPosition({
                    x: leftX,
                    y: 0
                });
            }
            if (rightDrag) {
                rightDrag.setPosition({
                    x: rightX,
                    y: 0
                });
            }
        };

        // 值改变后同步拖拽点的位置
        watchEffect(() => {
            value.value;
            updateHandlerPosition();
        });

        onMounted(() => {
            // 容器尺寸变化的时候改变值的位置
            const ob = new ResizeObserver(() => {
                updateHandlerPosition();
            });

            ob.observe(wrap.value);

            onBeforeUnmount(() => ob.disconnect());
        });

        const toFixed = (num: number) => {
            let r;
            try {
                r = step.toString().split('.')[1].length;
            } catch (e) {
                r = 0;
            }
            const m = Math.pow(10, r);
            return Math.round(num * m) / m;
        };

        //左侧拖拽
        const onLeftDrag = (e: any, data: any) => {
            const railRect = rail.getBoundingClientRect();
            const allW = railRect.width;
            const v = toFixed(data.x / allW * (max - min) + min);
            setTimeout(() => {
                pop && pop.updatePosition();
            });
            if (range && v > value.value[1]) {
                return false;
            }
            const val = range ? [v, Math.max(v, value.value[1])] : v;
            value.value = val;
            emit('change', val);
        };

        //右侧拖拽
        const onRightDrag = (e: any, data: any) => {
            const railRect = rail.getBoundingClientRect();
            const allW = railRect.width;
            const v = toFixed(data.x / allW * (max - min) + min);
            setTimeout(() => {
                popRight && popRight.updatePosition();
            });
            if (range && v < value.value[0]) {
                return false;
            }
            const val = range ? [Math.min(value.value[0], v), v] : v;
            value.value = val;
            emit('change', val);
        };

        // 点击后改变值
        const onMouseDown = (e: any) => {
            if (props.disabled) {
                return;
            }
            if (e.target.classList.contains('cm-slider-handle')) {
                return;
            }
            const slider = e.target.closest('.cm-slider');
            if (!slider) {
                return;
            }
            const sliderRect = slider.getBoundingClientRect();
            const x = e.pageX - sliderRect.left;

            const railRect = rail.getBoundingClientRect();
            const allW = railRect.width;
            const stepNum = Math.round(x / allW * (max - min) / step);
            const v = toFixed(stepNum * step + min);

            let val = value.value;

            if (range) {
                const nearLeft = Math.abs(val[1] - v) > Math.abs(val[0] - v);
                val = nearLeft ? [v, val[1]] : [val[0], v];
                value.value = val;
                setTimeout(() => {
                    nearLeft ? pop && pop.updatePosition()
                        : popRight && popRight.updatePosition();
                });
                emit('change', val);
            } else {
                value.value = v;
                setTimeout(() => {
                    pop && pop.updatePosition();
                });
                emit('change', v);
            }
        };

        const steps = computed(() => {
            if (!props.marks) {
                return [];
            }
            const arr = [];
            for (let i = min; i <= max; i += step) {
                if (props.marks[i]) {
                    arr.push(i);
                }
            }
            return arr;
        });

        const marks = computed(() => {
            if (props.marks) {
                const arr = [];
                for (const step in props.marks) {
                    arr.push({
                        step: parseFloat(step),
                        label: props.marks[step]
                    });
                }
                return arr;
            }
            return [];
        });

        return () => <div class={classList.value} onMousedown={onMouseDown} ref={wrap}>
            <div class="cm-slider-rail" ref={(el) => rail = el} ></div>
            <div class="cm-slider-track" style={trackStyle.value}></div>
            <div class="cm-slider-steps">
                {
                    steps.value.map(item => {
                        const ranges = range ? value.value : [min, value.value];
                        const isActive = item >= ranges[0] && item <= ranges[1];
                        const stepClass = () => ({
                            'cm-slider-step': true,
                            'cm-slider-step-active': isActive
                        });
                        const left = `${((item - min) / (max - min)) * 100}%`;
                        return <span class={stepClass()} style={{left}}></span>;
                    })
                }
            </div>
            <Popover disabled={props.disabled} content={contextLeft.value} align="top" ref={(el) => pop = el} arrow>
                <Draggable axis="x" disabled={props.disabled} ref={(el) => leftDrag = el} onDrag={onLeftDrag} bounds="parent" class="cm-slider-handle-drag"
                    grid={[snap(), snap()]}>
                    <div class="cm-slider-handle" tab-index="0" />
                </Draggable>
            </Popover>

            {
                range
                    ? <Popover disabled={props.disabled} content={contextRight.value} align="top" ref={(el) => popRight = el} arrow>
                        <Draggable axis="x" disabled={props.disabled} ref={(el) => rightDrag = el} onDrag={onRightDrag} bounds="parent" class="cm-slider-handle-drag"
                            grid={[snap(), snap()]}>
                            <div class="cm-slider-handle" tab-index="1" />
                        </Draggable>
                    </Popover>
                    : null
            }
            {
                props.marks
                    ? <div class="cm-slider-marks">
                        {
                            marks.value.map(item => {
                                const left = `${((item.step - min) / (max - min)) * 100}%`;
                                return <span class="cm-slider-mark" style={{left}}>{item.label}</span>;
                            })
                        }
                    </div>
                    : null
            }
        </div>;
    }
});
